home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Network Supervisor's Toolkit
/
Network Supervisor's Toolkit.iso
/
tools
/
nwtp06
/
blts9401.txt
< prev
next >
Wrap
Text File
|
1996-07-10
|
33KB
|
759 lines
ARTICLES: IPX/SPX for NetBIOS Developers
Original article: (c) Copyright Novell, 1994
Novell Professional Developer BULLETS
January 1994 (Volume 6, Number 1)
NwTP additions : (between angular brackets/ minus signs [- ... -])
NetBIOS is a popular peer-to-peer communication method that it is
supported under NetWare through a NetBIOS emulator. However, even though
NetBIOS is supported, there are definite advantages to using Novell's
"native tongue" protocols, IPX (Internet Packet eXchange) and SPX
(Sequenced Packet eXchange), when doing peer-to-peer communication.
This article discusses the advantages of using IPX/SPX and provides an
introduction to Novell's IPX and SPX protocols for developers who have a
working familiarity with NetBIOS.
Why Use IPX/SPX?
The most obvious reason to use IPX and SPX is to improve performance;
since NetWare emulates NetBIOS, processing NetBIOS commands involves more
overhead than processing IPX/SPX commands. NetWare encapsulates emulated
NetBIOS packets within IPX packets before they go out on the wire, so
moving to IPX/SPX allows you to "cut out the middleman."
You lose no connectivity by switching protocols either, since the
emulated NetBIOS layer cannot communicate with hardware NetBIOS systems.
In fact, moving to IPX/SPX gives you a net gain in connectivity; NetWare
has a 70% share of the network operating system market.
Also, since the NetBIOS emulator adds an additional layer of complexity
to packets being sent out, it is more difficult to troubleshoot problems.
Emulating NetBIOS involves an extra driver and an extra set of potential
incompatibilities. Generally speaking, since IPX and SPX are not
dramatically different from NetBIOS, it makes your job easier to work
with the protocols that NetWare is designed to support.
Datagram Services
Novell's IPX protocol provides almost the same functionality as NetBIOS
datagrams. Both specifications deliver packets on a best-effort basis,
but with no guarantee of delivery or sequencing. Both IPX and NetBIOS
also provide the capability to send packets either to a single node or to
multiple nodes. NetBIOS supports the multicast, or the sending of a
datagram to a selected group of nodes with the same group name. Since IPX
is address-based instead of name-based, this capability is not directly
supported; instead IPX must send an individual packet to each node.
NetBIOS also supports the broadcast datagram, a datagram that is
broadcast to the entire internetwork. IPX supports broadcasts, but only
to one subnet at a time. Usually, this restriction poses no problem,
since mechanisms such as the NetWare Service Advertising Protocol (SAP)
overcome this limitation.
The data portion of a NetBIOS datagram is limited in length to 512 bytes,
whereas IPX packets allow 546 bytes of data on all networks and can
sometimes be substantially larger than that depending on the maximum
packet size supported by network routers. Some networks can handle packet
sizes of 4096 bytes or more.
Session Services
As in the relationship between IPX and NetBIOS datagrams, Novell's SPX
protocol serves much the same function as the NetBIOS session. Both SPX
and NetBIOS sessions provide guaranteed delivery and sequencing of
packets, but at the cost of increased overhead.
The primary difference between the two is the supported packet size.
NetBIOS sessions support 64K packet sizes (128K with Chain Sends). SPX
has the same 546-byte packet size limitation as IPX and, in fact, SPX
allows slightly less data in a packet than IPX, since the SPX header
requires an additional 12 bytes. SPX therefore supports 534 bytes of data
on all networks with the potential for much larger packets if supported
by the routers, although attaining a 64K packet size is unlikely.
Probably the most noticeable difference between IPX/SPX and NetBIOS is
how each addresses packets. IPX/SPX addresses packets using network,
node, and socket numbers. NetBIOS uses unique names to address packets.
Each workstation can be uniquely addressed using the network and node
numbers.
A workstation can then have as many open sockets as desired for receiving
peer-to-peer data packets. Many methods exist for determining a
workstation's network, node, and destination socket number, but for
simplicity the example code in this article uses SAP to obtain this
information.
The Waiting Game
With NetBIOS, you can choose to allow most NetBIOS commands to complete
before returning control to the application, but most IPX/SPX commands
return control immediately. In other words, most IPX/SPX commands are
"no-wait" commands; there is no IPX/SPX "wait" counterpart.
Since most NetBIOS developers use the "no-wait" variants, this difference
should not pose a problem, but if you need to use a "wait," you can code
it very simply by issuing the command and then looping on the in use
field.
Asynchronous Events
IPX/SPX also has a feature that is not used with NetBIOS: the
asynchronous event. An asynchronous event can be initiated at any time
and, as the name implies, can be set to occur independent of an
application's execution path. An event could be set up, for example, to
automatically broadcast an IPX packet every 45 seconds. The application
initiating this event could then continue processing and leave the timing
and broadcasting of packets to the IPX event handler.
The Network Control Block & the Event Control Block
From a developer's perspective, the "core" of NetBIOS is the Network
Control Block (NCB). IPX and SPX are based on an Event Control Block
(ECB) and an IPX/SPX header. Figure 1 describes the fields in the ECB.
*********************************************************
Figure 1: The IPX/SPX Event Control Block [- C structure -]
void far *linkAddress Set by IPX
void (far *ESRAddress)() Equivalent to NetBIOS POST routine
BYTE inUseFlag Set when the ECB is in use, zero
when it is available
BYTE completionCode Equivalent to NetBIOS Command
Completion
WORD socketNumber Socket number associated with ECB
BYTE IPXWorkspace[4] Set by IPX
BYTE driverWorkspace[12] Set by IPX
BYTE immediateAddress[6] Node address of next "hop"
WORD fragmentCount Number of buffer fragments in packet
ECBFragment fragmentDescriptor[2] Address and size of fragment(s)
END of FIGURE 1
*********************************************************
[- *********************************************************
Figure 1a: The IPX/SPX Event Control Block (Pascal syntax)
linkAddress :Pointer Set by IPX
ESRAddress :Pointer Equivalent to NetBIOS
POST routine
InUseFlag :Byte; Set when the ECB is in use,
zero when it is available
CompletionCode :Byte; Equivalent to NetBIOS Command
Completion
SocketNumber :Word; Socket number associated
with ECB
IPXWorkspace :array[1..4] of byte; Set by IPX
DriverWorkspace :array[1..12] of byte; Set by IPX
ImmediateAddress:array[1..6] of byte; (Tnodeaddress)
Node address of next "hop"
FragmentCount :word; Number of buffer fragments
in packet
Fragment :array[1.. ] of Tfragment Address and size of
fragment(s)
(Note: this structure is declared as the Tecb type in the nwIPX unit)
END of FIGURE 1a
********************************************************* -]
Note that the ECB contains a field that has no equivalent in the NCB
called the immediate address field. This field should be populated with
the node address of the first "hop" on the way to the packet's ultimate
destination. Novell provides an API call to populate this field, the
IPXGetLocalTarget() API available in the NetWare Client SDK.
IPX Send Example
The sample code in this article includes simple examples written under
DOS with the NetWare Client SDK. Figure 2 shows a routine sending an IPX
packet.
*********************************************************
Figure 2: IPX Send [- C example -]
/* Send "Hello!" to the station at network 0x11111111, node
0x222222222222, socket 0x3333 using IPX */
void IPXSayHello()
{
char buffer[] = "Hello!";
ECB ecb;
IPXHeader header;
int transTime;
header.packetType = 4;
memset(header.destination.network, 0x11, 4);
memset(header.destination.node, 0x22, 6);
memset(header.destination.socket, 0x33, 2);
ecb.ESRAddress = NULL;
ecb.socketNumber = 0x4444;
IPXGetLocalTarget(header.destination,
ecb.immediateAddress, &transTime);
ecb.fragmentCount = 2;
ecb.fragmentDescriptor[0].address = &header;
ecb.fragmentDescriptor[0].size = sizeof(IPXHeader);
ecb.fragmentDescriptor[1].address = buffer;
ecb.fragmentDescriptor[1].size = strlen(buffer) + 1;
IPXSendPacket(&ecb);
}
END of FIGURE 2
*********************************************************
[- *********************************************************
Figure 2a: IPX Send (Pascal example)
{ Send "Hello!" to the station at network $11111111, node
$222222222222, socket $3333 using IPX }
Procedure IPXSayHello;
Var buffer:string;
ecb:Tecb;
header:TipxHeader;
transTime:Word;
begin
Buffer:="Hello!";
header.packetType := 4;
FillChar(header.destination.network,4,$11);
FillChar(header.destination.node, 6,$22);
FillChar(header.destination.socket, 2,$33);
ecb.ESRAddress:=NIL;
ecb.socketNumber:=$4444;
IPXGetLocalTarget(header.destination,
ecb.immediateAddress, transTime);
ecb.fragmentCount:=2;
ecb.fragment[1].address:= @header;
ecb.fragment[1].size := SizeOf(TIPXHeader);
ecb.fragment[2].address:= @buffer[1];
ecb.fragment[2].size:= ord(buffer[0]);
IPXSendPacket(ecb);
end;
END of FIGURE 2a
********************************************************* -]
The first apparent difference between IPX and NetBIOS is that IPX uses
two buffers where NetBIOS would use one. The first buffer is the IPX
Header containing the source and destination addresses, the packet type,
and several "housekeeping" fields. Refer to Figure 3 for a description of
the IPX header.
*********************************************************
Figure 3: IPX Header
WORD checkSum Included to conform to Xerox IDP standard
Set to FFFF by IPX
WORD length Length of entire IPX packet including
header
Set by IPX
BYTE transportControl Hop count - Set to zero by IPX
BYTE packetType IPX packet type is 4
IPXAddress destination Address the packet is sent to
[- Pascal: of type TInternetworkAddress -]
IPXAddress source Address of node sending packet set by IPX
[- Pascal: of type TinternetworkAddress -]
END of FIGURE 3
*********************************************************
The second buffer is the data to be sent. Two fields in the IPX header
must be set for an IPX send: the packet type and the destination address.
IPX packets are type 4, SPX packets are type 5. [- Note that according
to the original xerox definitions this statement is not correct. Type 4
packets are reserved for the PEP protocol. Use type 0 (undefined) when
transmitting standard IPX packets-] The destination address consists
of a four-byte network number, a six-byte node number, and a two-byte
socket number.
If these examples used an Event Service Routine (ESR), the ESR address
would be filled with the address of a procedure to be run when the send
completes, but since NULL is specified, this routine will not be run. The
ESR is equivalent to the NetBIOS POST routine. When the IPX send
executes, the rest of the fields in the IPX header are filled in
automatically, including the source address. You must specify the socket
number to be included in the source address, but the socket need not be
open to send a packet. For this example, socket number 0x4444 was
arbitrarily chosen.
The immediate address field described above must be filled in as well,
and the IPXGetLocalTarget() API call fills in this field with the
appropriate value. It is passed the final destination of the packet and
it calculates the address of the "first hop" on the way to the final
destination. Note that if the target workstation is on the same subnet as
the sending workstation the immediate address will be the same as the
final destination. Otherwise, it will be a bridge or router on the
subnet.
Each of the buffers sent in the IPX packet is considered to be a
fragment. Since there are two buffers (the IPX header and the data), the
fragment count is equal to two. The address and size of the fragments are
then entered, starting with the IPX header. As soon as all of the
relevant fields are filled, the example calls IPXSendPacket() and passes
it the address of the ECB.
Receiving an IPX packet is much like sending one from a programming
standpoint, except that you do not need to set the IPX header fields. In
the ECB, you should set the ESR address, socket number, immediate
address, and fragment descriptors.
Note about socket numbers: the socket number specified for an IPX send
does not need to be open, but for an IPX receive the socket must be open.
The API call to receive an IPX packet is IPXListenForPacket().
SPX Connection Example
Figure 4 contains a code sample that establishes an SPX connection.
Before the request for an SPX connection is submitted, several ECBs are
already listening for data (this is important). SPX temporarily "steals"
two ECBs from the available and waiting ones for connection maintenance,
and then it puts the stolen ECBs back in the pool when finished. If there
are no pending ECBs for SPX to use, it cannot send an acknowledgement to
the remote site and the connection will stall and time out.
*********************************************************
Figure 4: Establishing an SPX Connection [- C code example -]
/* Start an SPX connection with the station at network
0x11111111, node 0x222222222222, socket 0x3333, use
local socket 0x4444 */
#define NUM_BUFFS 5
void call()
{
ECB send, receive[NUM_BUFFS], connect, term;
SPXHeader sendHdr, rcvHdr[NUM_BUFFS], connHdr;
char buffer[NUM_BUFFS][80], sendbuf[] = "Hello!";
int i, ccode, packetsReceived;
WORD spxConnectionID;
for (i = 0; i < NUM_BUFFS; i++) {
receive[i].ESRAddress = NULL;
receive[i].socketNumber = 0x4444;
receive[i].fragmentCount = 2;
receive[i].fragmentDescriptor[0].address
= &(rcvHdr[i]);
receive[i].fragmentDescriptor[0].size
= sizeof(SPXHeader);
receive[i].fragmentDescriptor[1].address
= &(buffer[i]);
receive[i].fragmentDescriptor[1].size = 80;
SPXListenForSequencedPacket(receive[i]);
}
connect.ESRAddress = NULL;
connect.socketNumber = 0x4444;
connect.fragmentCount = 1;
connect.fragmentDescriptor[0].address = &connHdr;
connect.fragmentDescriptor[0].size
= sizeof(SPXHeader);
memset(connHdr.destination.network, 0x11, 4);
memset(connHdr.destination.node, 0x22, 6);
memset(connHdr.destination.socket, 0x33, 2);
ccode = SPXEstablishConnection(0, 0,
&spxConnectionID,
&connect);
printf("SPXEstablishConnection return code
= 0x%x\n", ccode);
if (ccode != 0)
return;
while (connect.inUseFlag != 0)
IPXRelinquishControl();
if (connect.completionCode != 0)
return;
send.ESRAddress = NULL;
send.fragmentCount = 2;
send.fragmentDescriptor[0].address = &sendHdr;
send.fragmentDescriptor[0].size = sizeof(SPXHeader);
send.fragmentDescriptor[1].address = sendbuf;
send.fragmentDescriptor[1].size = 7;
SPXSendSequencedPacket(spxConnectionID, &send);
packetsReceived = 0;
while (packetsReceived < 10) {
for (i = 0; i < NUM_BUFFS; i++) {
if (receive[i].inUseFlag != 0) {
if (receive[i].completionCode != 0) {
packetsReceived = 10;
/* If we get an error, terminate */
break;
}
printf("Received: %s\n", buffer[i]);
packetsReceived++;
}
SPXListenForSequencedPacket(receive[i]);
}
IPXRelinquishControl();
}
term.ESRAddress = NULL;
term.fragmentCount = 1;
term.fragmentDescriptor[0].address = &connHdr;
term.fragmentDescriptor[0].size = sizeof(SPXHeader);
SPXTerminateConnection(spxConnectionID, &term);
while (term.inUseFlag != 0)
IPXRelinquishControl();
for (i = 0; i < NUM_BUFFS; i++)
IPXCancelEvent(receive[i]);
}
END of FIGURE 4
*********************************************************
[- *********************************************************
Figure 4a: Establishing an SPX Connection (Pascal example)
{ Start an SPX connection with the station at network
$11111111, node $222222222222, socket $3333, use
local socket $4444 }
CONST NUM_BUFFS=5;
Procedure call;
Var send,connect,term:Tecb;
receive :array[1..NUM_BUFFS] of Tecb;
sendHdr, connHdr : TspxHeader;
rcvHdr :array[1..NUM_BUFFS] of TspxHeader;
buffer :array[1..NUM_BUFFS] of string[80];
sendBuf :string;
i,packetsReceived:Integer;
spxConnectionId :word;
begin;
sendbuf:="Hello!";
for i:= 1 to NUM_BUFFS
do begin
receive[i].ESRAddress := NIL;
receive[i].socketNumber := $4444;
receive[i].fragmentCount = 2;
receive[i].fragment[1].address := @rcvHdr[i];
receive[i].fragment[1].size := sizeof(TSPXHeader);
receive[i].fragment[2].address := @buffer[i];
receive[i].fragment[2].size := 80;
SPXListenForSequencedPacket(receive[i]);
end;
connect.ESRAddress := NIL;
connect.socketNumber := $4444;
connect.fragmentCount := 1;
connect.fragment[1].address := @connHdr;
connect.fragment[1].size := sizeof(TSPXHeader);
FillChar(connHdr.destination.network, 4, $11);
FillChar(connHdr.destination.node, 6, $22);
FillChar(connHdr.destination.socket, 2, $33);
IF NOT SPXEstablishConnection(0, 0,
spxConnectionID,
connect)
then begin
writeln('SPXEstablishConnection return code',
HexStr(nwSpx.result,2));
exit;
end;
while (connect.inUseFlag <> 0)
do IPXRelinquishControl();
if (connect.completionCode <> 0)
then exit;
send.ESRAddress := NIL;
send.fragmentCount := 2;
send.fragment[1].address = @sendHdr;
send.fragment[1].size := sizeof(TSPXHeader);
send.fragment[2].address := @sendbuf[0];
send.fragment[2].size := ord(sendBuf[0])+1;
SPXSendSequencedPacket(spxConnectionID, send);
packetsReceived := 0;
while (packetsReceived < 10)
do begin
for i :=1 to NUM_BUFFS
do begin
if (receive[i].inUseFlag <> 0)
and (receive[i].completionCode <> 0)
then begin
packetsReceived := 10;
exit;
{ If we get an error, terminate }
end;
writeln('Received: ", buffer[i]);
inc(packetsReceived);
SPXListenForSequencedPacket(receive[i]);
end;
IPXRelinquishControl;
end;
term.ESRAddress := NIL;
term.fragmentCount := 1;
term.fragment[1].address := @connHdr;
term.fragment[1].size := sizeof(TSPXHeader);
SPXTerminateConnection(spxConnectionID, term);
while (term.inUseFlag <> 0)
do IPXRelinquishControl;
for i:=1 to NUM_BUFFS
do IPXCancelEvent(receive[i]);
end;
END of FIGURE 4a
********************************************************* -]
This process may sound complicated, but everything happens transparently.
As long as there are extra ECBs available, the application never knows
they have been borrowed, since SPX puts them back in the exact same state
they were in when they were pressed into service.
If the connection is established with the SPX watchdog enabled, the
watchdog monitors the connection and notifies the application if the
connection fails, even if the application is not currently sending data
over the connection. This feature is useful for applications that start
SPX connections, but use them infrequently. For simplicity, however, the
example does not use the SPX watchdog.
After the listen ECBs have been posted, the connection ECB is then set up
in much the same way the IPX send ECB was, except that this ECB has only
one fragment: the SPX header. The destination network, node, and socket
also are set the same way they were in the previous example.
SPXEstablishConnection() is passed a retry count of zero, indicating that
you should use the default value for number of retries. This value is set
in the workstation's NET.CFG file using the IPX RETRY COUNT parameter,
which defaults to 20. The last zero passed in SPXEstablishConnection()
indicates not to use the SPX watchdog. The SPX connection ID is returned
as the third parameter. The SPX connection ID can be considered
equivalent to the NetBIOS local session number.
Next, the sample code attempts to establish a connection. It polls the
ECB's in use flag waiting for the event to complete. The
IPXRelinquishControl() call is very important at this stage. If the code
did nothing but sit in a tight loop, IPX and SPX would never get the
chance to do any processing. IPXRelinquishControl() allows the IPX/SPX
layer to get some work done.
Once the in use flag is set to zero, the example checks the return code
to see if the attempt to establish a connection was successful. The code
does not illustrate how to handle the various failure cases, but the most
likely cause of a failure would be that the other side is not yet
listening for a connection, just like in NetBIOS. After establishing the
connection, packets can be sent to the remote station.
The SPXSendSequencedPacket() call requires much less information than its
IPX counterpart. Since the connection is already established, all
SPXSendSequencedPacket() needs is the SPX connection ID, an ESR address,
and the fragment information.
After sending a packet, the example program waits for ten packets to
arrive. When an ECB comes back, the example displays the data and then
re-submits the ECB so that it can be used to receive a packet again.
After receiving ten packets, it issues an SPXTerminateConnection() call
to notify the other side that it is done.
The call to terminate the connection takes almost the same parameters
that the establish connection call does, except that there is no need to
fill out any information in the SPX header. Once the connection has been
terminated, the pending listen ECBs must be cancelled. To do so, the
example calls IPXCancelEvent(). Unlike most other ECB-related calls,
IPXCancelEvent() does not return until the ECB has been cancelled so
there is no need to poll the in use flag.
Event Service Routines
Event Service Routines (ESRs) serve the same purpose as the NetBIOS POST
routines, but require a little more setup than the standard POST routine.
Most ESRs are written in Assembly, although some call C functions.
Figure 5 shows a generic ESR that calls a C function after allocating its
own stack. This is very important since the amount of free stack space
(if any) at interrupt time is unknown, and any attempt by a C function to
use the stack could result in memory corruption if the stack is
overflowed. The only way to guarantee that this will not occur is to
allocate sufficient stack space in the ESR.
*********************************************************
Figure 5: Example Event Service Routine (ESR) [- C/ASM code -]
.MODEL LARGE
public _ReceiveESRHandler
extrn _ProcessReceiveData:PROC
.DATA
; The stack segment and pointer must be saved so that you can set up
; your own stack.
stk_seg dw 0 ; variable to store old stack segment
stk_ptr dw 0 ; variable to store old stack pointer
stk_stk dw 512 dup (0) ; new stack of 1024 bytes in length
stk_end dw 0 ; the end of the stack
.CODE
; @datasize is TRUE if the model is MEDIUM or LARGE and FALSE if the
; model is SMALL or COMPACT. Just modify the .MODEL ???? above for the
; model you want. ES/SI holds the seg/offset of the currently used ECB
; that ProcessReceivedData needs to process.
_ReceiveESRHandler PROC far
mov ax,DGroup
mov ds,ax
mov stk_seg,ss ; Save the stack segment
mov stk_ptr,sp ; Save the stack pointer
mov ss,ax ; move the segment of new_stk into ss
mov sp,offset stk_end ; move offset of new_stk to sp
IF @datasize
push es ; push es if mem. model medium/large
ENDIF
push si
call _ProcessReceivedData
mov ss,stk_seg ; Restore old stack segment
mov sp,stk_ptr ; Restore old stack pointer
retf
_ReceiveESRHandler ENDP
END END of FIGURE 5
*********************************************************
[- *********************************************************
Figure 5a: Example Event Service Routine (ESR) (BASM/Pascal)
{ The stack segment and pointer must be saved so that you can set up
your own stack. }
Var stk_stk:array[1..512] of word; { new stack of 1024 bytes in length }
stk_end:word; { the end of the stack }
{$F+}
Procedure ESRhandler(Var p:Tpecb); { * Type TPecb=^Tecb }
begin
.
.
end;
{$F-}
{$F+}
Procedure ListenESR; assembler;
asm { ES:SI are the only valid registers when entering this procedure ! }
mov dx, seg stk_stk { = seg @DATA }
mov ds, dx
mov dx,ss { setup of a new local stack }
mov bx,sp { ss:sp copied to dx:bx}
mov ax,ds
mov ss,ax
mov sp,offset stk_end
push dx { push old ss:sp on new stack }
push bx
push es { * push es:si on stack as local vars }
push si { * }
mov di,sp { * }
push ss { * push address of local ptr on stack }
push di { * }
CALL EsrHandler
add sp,4 { skip stack ptr-copy }
pop bx { restore ss:sp from new stack }
pop dx
mov sp,bx
mov ss,dx
end;
{$F-}
Note that a local stack of 1024 bytes (512 words) may not be large
enough for some applications calling other functions within the
ESRhandler. Increase the stacksize by 1024 bytes at a time to
determine the stack requirement.
END END of FIGURE 5a
********************************************************* -]
Figure 6 contains a code fragment demonstrating the use of an ESR. It
receives ten SPX packets just like the example in Figure 3 does, but it
uses an ESR instead of polling the in use flag. The assembly language
routine from Figure 4 is declared as the ESR, and it in turn calls the
C [-/Pascal-] function ProcessReceivedData().
*********************************************************
Figure 6: Using an Event Service Routine (ESR) [- C Code -]
int packetCount = 0;
void ProcessReceivedData(ECB *ecb)
{
packetCount++;
printf("%s\n", ecb->fragmentDescriptor[1].address);
SPXListenForSequencedPacket(ecb); /* Re-issue the listen */
}
main()
{
.
. /* This code is identical to SPX setup code in Fig. 4, except */
. /* for receive[i].ESRAddress line, which will be as follows: */
receive[i].ESRAddress = (void (far *) () ) ReceiveESRHandler;
.
. /* The send ECB does not normally use an ESR. */
.
while (packetCount < 10)
IPXRelinquishControl();
.
. /* Shut down connection, cancel ECBs */
.
}
END of FIGURE 6
*********************************************************
[- *********************************************************
Figure 6a: Using an Event Service Routine (ESR) (Pascal)
Var PacketCount;
Procedure ProcessReceivedData(Var ECB:Tecb)
begin
inc(packetCount);
writeln(string(ecb^.fragment[2].address^));
SPXListenForSequencedPacket(ecb); { Re-issue the listen }
end;
begin { main body }
PacketCount:=0;
.
. { This code is identical to SPX setup code in Fig. 4a, except }
. { for receive[i].ESRAddress line, which will be as follows: }
receive[i].ESRAddress := @ReceiveESRHandler;
.
. { The send ECB does not normally use an ESR. }
.
while (packetCount < 10)
do IPXRelinquishControl;
.
. { Shut down connection, cancel ECBs }
.
end;
END of FIGURE 6a
********************************************************* -]
IPX and SPX may look a little more complicated than NetBIOS at first, but
as soon as you begin using these protocols, you see how similar they
really are. Using IPX/SPX requires slightly more effort, but the
performance and compatibility gains when running under NetWare more than
compensate. If you are thinking about becoming more familiar with IPX and
SPX development, feel free to contact Novell's Developer Support group at
1-800-NETWARE (1-800-638-9273) or 1-801-429-5588.